#include "modulator.h"

#include <process.h>
#include "win_exception.h"

#include <ctime>
#include <iostream>
#include <fstream>

using namespace hard;

const double modulator::pi = 3.14159265358979323846264338327950288419716939937510;
const double modulator::next_note_factor = 1.0594630943592952645618252949463;
const double modulator::freq_A = 440;
const double modulator::freq_As = modulator::freq_A*modulator::next_note_factor;
const double modulator::freq_B = modulator::freq_As*modulator::next_note_factor;
const double modulator::freq_C = modulator::freq_B*modulator::next_note_factor;
const double modulator::freq_Cs = modulator::freq_C*modulator::next_note_factor;
const double modulator::freq_D = modulator::freq_Cs*modulator::next_note_factor;
const double modulator::freq_Ds = modulator::freq_D*modulator::next_note_factor;
const double modulator::freq_E = modulator::freq_Ds*modulator::next_note_factor;
const double modulator::freq_F = modulator::freq_E*modulator::next_note_factor;
const double modulator::freq_Fs = modulator::freq_F*modulator::next_note_factor;
const double modulator::freq_G = modulator::freq_Fs*modulator::next_note_factor;
const double modulator::freq_Gs = modulator::freq_G*modulator::next_note_factor;


//std::ofstream fout("samples.txt");

void modulator::update_samples()
{
  static double last_w=0;
  static double last_amplitude=0;

  double w = get_w();
  double amp = get_amplitude();
  bool changed=false;

  if(last_w != w)
  {
    last_w = w;
    changed = true;
  }
  if(last_amplitude != amp)
  {
    last_amplitude = amp;
    changed = true;
  }
  if(!changed)
    return;

  EnterCriticalSection(&crit_samples);
  for(size_t i=0; i<samples_.size(); ++i)
    //fout<<static_cast<int>(samples_[i] = static_cast<sample_type>( max_abs_amplitude * amp * sin(w*t) ))<<"\n";
    samples_[i] = static_cast<sample_type>( max_abs_amplitude * amp * sin(w*i/nSamplesPerSec) );

  LeaveCriticalSection(&crit_samples);
}


//VOID CALLBACK hard::TimerCallback(PVOID lpParam, BOOLEAN TimerOrWaitFired)
//{
//  modulator* m = (modulator*) lpParam;
//  if(TimerOrWaitFired)
//  {
//    time_t t =time(0);
//    std::cout<<ctime(&t)<<std::endl;
//    m->write_samples();
//    m->update_samples();
//  }
//  else
//    TimerOrWaitFired=TimerOrWaitFired;
//
//}

unsigned int WINAPI hard::modulator_thread(void* arg)
{
  try {
    modulator* m = (modulator*) arg;

    HANDLE lpHandles[2] = {m->endThread, m->timer};
    DWORD ret;
    while(true)
    {
      ret = WaitForMultipleObjects(2, lpHandles, FALSE, INFINITE);
      m->chr.start();
      X_WIN_THROW_IF(ret==WAIT_FAILED, "modulator_thread: WaitForMultipleObjects");

      if(ret-WAIT_OBJECT_0 == 0 || m->end || 0==WaitForSingleObject(m->endThread,0))
        break;
      
      if(ret-WAIT_OBJECT_0 == 1)
      {
        if(!(m->end || 0==WaitForSingleObject(m->endThread,0)))
        {
          m->write_samples();
          m->update_samples();
        }
        else
          break;
      }

      else
       throw std::runtime_error("modulator_thread: WaitForMultipleObjects unkwon event");

      m->chr.stop();
    }
    return 0;
  }
  catch(win_exception& e)
  {
    std::cout<<e.what()<<"\nerror number: "<<e.error_number()<<std::endl;
    return e.error_number();
  }
  catch(std::exception& e)
  {
    std::cout<<e.what()<<std::endl;
    return -1;
  }
}


modulator::modulator(size_t write_period, double carrier_freq, double carrier_amplitude, DWORD nSamplesPerSec)
  : out(nSamplesPerSec, 8*sizeof(sample_type), 1), w_(carrier_freq*pi*2), amplitude_(carrier_amplitude), write_period(write_period), 
    samples_(write_period*nSamplesPerSec/1000,0),
    nSamplesPerSec(nSamplesPerSec), end(false), chr(write_period), destroy(true)
{
  InitializeCriticalSection(&crit_amplitude);
  InitializeCriticalSection(&crit_w);
  InitializeCriticalSection(&crit_samples);

  update_samples();

  //timer_queue = CreateTimerQueue();
  //X_WIN_THROW_IF_NULL(timer, "modulator::modulator: CreateTimerQueue");

  //write_samples();

  //X_WIN_THROW_IF_NULL(CreateTimerQueueTimer(&timer, timer_queue, TimerCallback, this, 
  //                                          0, write_period, 0),
  //  "modulator::modulator: CreateTimerQueueTimer");

  //SleepEx(INFINITE, TRUE);

  timer = CreateWaitableTimer(NULL, FALSE, NULL);
  X_WIN_THROW_IF_NULL(timer, "modulator::modulator: CreateWaitableTimer");
  LARGE_INTEGER Preset;
  Preset.QuadPart = -1;
  X_WIN_THROW_IF_NULL(SetWaitableTimer(timer, &Preset, write_period, NULL, NULL, FALSE), "modulator::modulator: SetWaitableTimer");

  endThread = CreateEvent(NULL, TRUE, FALSE, NULL);
  X_WIN_THROW_IF_NULL(endThread, "modulator: CreateEvent");

  thread = (HANDLE) _beginthreadex(NULL, 0, modulator_thread, this, 0, &threadId);
  X_WIN_THROW_IF_NULL(thread, "modulator: CreateThread");

  X_WIN_THROW_IF_NULL(SetThreadPriority(thread,THREAD_PRIORITY_TIME_CRITICAL), "tracker: SetThreadPriority");
}

modulator::~modulator()
{
  if(!destroy)
    return;

  end = true;

  X_WIN_THROW_IF_NULL(SetEvent(endThread), "~modulator: SetEvent(endThread)");

  DWORD ret=STILL_ACTIVE;
  while(true)
  {
    X_WIN_THROW_IF_NULL(GetExitCodeThread(thread,&ret), "~modulator: WaitForSingleObject");
    if(ret!=STILL_ACTIVE)
      break;
    Sleep(write_period);
  }

  X_WIN_THROW_IF_NULL(CloseHandle(thread), "~modulator: CloseHandle(thread)");
  X_WIN_THROW_IF_NULL(CloseHandle(endThread), "~modulator: CloseHandle(endThread)");
  X_WIN_THROW_IF_NULL(CloseHandle(timer), "modulator::~modulator: CloseHandle(timer)");
  
  //X_WIN_THROW_IF_NULL(DeleteTimerQueueTimer(timer_queue,timer,INVALID_HANDLE_VALUE),"modulator::~modulator: DeleteTimerQueueTimer");
  //X_WIN_THROW_IF_NULL(DeleteTimerQueue(timer_queue),"modulator::~modulator: DeleteTimerQueue");
  DeleteCriticalSection(&crit_amplitude);
  DeleteCriticalSection(&crit_w);
  DeleteCriticalSection(&crit_samples);
}